package com.dataart.training.crimequalification.xmltools;

import com.dataart.training.crimequalification.domain.*;
import com.dataart.training.crimequalification.entity.*;
import com.dataart.training.crimequalification.service.CrimeConsequenceService;
import com.dataart.training.crimequalification.service.CrimeObjectService;
import com.dataart.training.crimequalification.service.CrimeSubjectService;
import com.dataart.training.crimequalification.service.SubjectiveSideService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Service
public class XMLCrimeDetailsRepresentationServiceImpl implements XMLCrimeDetailsRepresentationService {

    //-----------------------------------------------------------------

    private static final Logger logger = LoggerFactory
            .getLogger(XMLCrimeDetailsRepresentationServiceImpl.class);

    private static final String EMPTY_TEXT_NODE_VAL = "NONE";

    private static final String XML_BASE_PATH = "D:\\crimequalification\\xml\\";
    private static final String CODEX_PATH = XML_BASE_PATH + "codex";


    //------------------------------------------------------------------

    @Autowired
    private CrimeObjectService crimeObjectService;

    @Autowired
    private CrimeSubjectService crimeSubjectService;

    @Autowired
    private CrimeConsequenceService crimeConsequenceService;

    @Autowired
    private SubjectiveSideService subjectiveSideService;

    //------------------------------------------------------------------

    private static String generateXMLFileName() {
        return new StringBuilder().append("XMLCrimeDetailsRepresentation ").
                append(new Date().hashCode()).append(".xml").toString();
    }

    /**
     * @param crimeDetails
     * @throws ParserConfigurationException
     */
    public String crimeDetailsToXML(CrimeDetails crimeDetails) throws ParserConfigurationException, TransformerException {

        DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = docFactory.newDocumentBuilder();

        // root elements
        Document doc = docBuilder.newDocument();
        Element rootElement = doc.createElement("crime-details");
        doc.appendChild(rootElement);

        // object elements
        Element crimeObject = doc.createElement("crime-object");
        rootElement.appendChild(crimeObject);

        // main object
        Element mainObject = doc.createElement("main-crime-object");
        mainObject.appendChild(doc.createTextNode(crimeDetails.getCrimeObject().getCrimeObjectEntity().getCodeName()));
        crimeObject.appendChild(mainObject);

        // facultative object

        Element facultativeObject = doc.createElement("facultative-crime-object");

        if (crimeDetails.getCrimeObject().isWithFacultativeObject()) {
            facultativeObject.appendChild(doc.createTextNode(crimeDetails.getCrimeObject().
                    getFacultativeCrimeObjectEntity().getCodeName()));

        } else {
            facultativeObject.appendChild(doc.createTextNode(EMPTY_TEXT_NODE_VAL));
        }

        crimeObject.appendChild(facultativeObject);

        // supplementary object

        Element supplementaryObject = doc.createElement("supplementary-crime-object");

        if (crimeDetails.getCrimeObject().isWithSupplementaryObject()) {
            supplementaryObject.appendChild(doc.createTextNode(crimeDetails.getCrimeObject().
                    getSupplementaryCrimeObjectEntity().getCodeName()));

        } else {
            supplementaryObject.appendChild(doc.createTextNode(EMPTY_TEXT_NODE_VAL));
        }

        crimeObject.appendChild(supplementaryObject);

        // subject

        Element crimeSubject = doc.createElement("crime-subject");
        crimeSubject.appendChild(doc.createTextNode(crimeDetails.getCrimeSubject().getCodeName()));
        rootElement.appendChild(crimeSubject);

        // consequences

        Element crimeConsequence = doc.createElement("crime-consequence");
        crimeConsequence.appendChild(doc.createTextNode(crimeDetails.getCrimeConsequence().getCodeName()));
        rootElement.appendChild(crimeConsequence);

        // subjective side

        Element subjectiveSide = doc.createElement("subjective-side");
        rootElement.appendChild(subjectiveSide);

        // guilt

        Element guilt = doc.createElement("guilt");
        subjectiveSide.appendChild(guilt);

        // intent

        Element intent = doc.createElement("intent");

        if (crimeDetails.getSubjectiveSide().getGuilt().isWithIntent()) {
            intent.appendChild(doc.createTextNode(crimeDetails.getSubjectiveSide()
                    .getGuilt().getIntent().getCodeName()));

        } else {
            intent.appendChild(doc.createTextNode(EMPTY_TEXT_NODE_VAL));
        }

        guilt.appendChild(intent);

        // indiscretion

        Element indiscretion = doc.createElement("indiscretion");

        if (crimeDetails.getSubjectiveSide().getGuilt().isWithIndiscretion()) {
            indiscretion.appendChild(doc.createTextNode(crimeDetails.getSubjectiveSide()
                    .getGuilt().getIndiscretion().getCodeName()));

        } else {
            indiscretion.appendChild(doc.createTextNode(EMPTY_TEXT_NODE_VAL));
        }

        guilt.appendChild(indiscretion);

        // motive

        Element motive = doc.createElement("motive");

        if (crimeDetails.getSubjectiveSide().isWithMotive()) {
            motive.appendChild(doc.createTextNode(crimeDetails.getSubjectiveSide()
                    .getMotive().getCodeName()));

        } else {
            motive.appendChild(doc.createTextNode(EMPTY_TEXT_NODE_VAL));
        }

        subjectiveSide.appendChild(motive);

        // purpose

        Element purpose = doc.createElement("purpose");

        if (crimeDetails.getSubjectiveSide().isWithPurpose()) {
            purpose.appendChild(doc.createTextNode(crimeDetails.getSubjectiveSide()
                    .getPurpose().getCodeName()));

        } else {
            purpose.appendChild(doc.createTextNode(EMPTY_TEXT_NODE_VAL));
        }

        subjectiveSide.appendChild(purpose);

        // delinquent state

        Element delState = doc.createElement("delinquent-state");

        if (crimeDetails.getSubjectiveSide().isWithDelinquentState()) {
            delState.appendChild(doc.createTextNode(crimeDetails.getSubjectiveSide()
                    .getDelinquentState().getCodeName()));

        } else {
            delState.appendChild(doc.createTextNode(EMPTY_TEXT_NODE_VAL));
        }

        subjectiveSide.appendChild(delState);


        // write the content into xml file
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer();
        DOMSource source = new DOMSource(doc);
        String fileName = generateXMLFileName();

        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");

        StreamResult result = new StreamResult(new File(XML_BASE_PATH + fileName));

        transformer.transform(source, result);

        logger.debug("New XML file \"" + fileName + "\" + created at " + new Date().toString());

        return XML_BASE_PATH + fileName;

    }

    /**
     * @param pathToFile
     * @return
     */
    public CrimeDetails parseXMLForCrimeDetails(String pathToFile) {

        CrimeDetails crimeDetails = new CrimeDetails();

        CrimeObject crimeObject = new CrimeObject(); // cannot be null
        MainCrimeObjectEntity mainCrimeObject = new MainCrimeObjectEntity(); // cannot be null too
        FacultativeCrimeObjectEntity facultativeCrimeObject = new FacultativeCrimeObjectEntity();
        SupplementaryCrimeObjectEntity supplementaryCrimeObject = new SupplementaryCrimeObjectEntity();
        CrimeSubjectEntity crimeSubject; // cannot be null either
        CrimeConsequenceEntity crimeConsequence;

        IntentEntity intent;
        IndiscretionEntity indiscretion;
        MotiveEntity motive;
        PurposeEntity purposeEntity;
        DelinquentStateEntity delinquentState;

        SubjectiveSide subjSide;

        try {

            File fXmlFile = new File(pathToFile);
            DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
            Document doc = dBuilder.parse(fXmlFile);

            doc.getDocumentElement().normalize();

            logger.debug("Root element :" + doc.getDocumentElement().getNodeName());

            Node crimeObjectNode = doc.getElementsByTagName("crime-object").item(0);

            if (crimeObjectNode.getNodeType() == Node.ELEMENT_NODE) {

                Element crimeObjectElement = (Element) crimeObjectNode;

                // getting main crime object
                String mainCrimeObjectCodeName = crimeObjectElement.getElementsByTagName("main-crime-object").
                        item(0).getTextContent();

                logger.debug(mainCrimeObjectCodeName);

                mainCrimeObject = crimeObjectService.getMainCrimeObjectByCodename(mainCrimeObjectCodeName);

                // getting facultative crime object

                String facultativeCrimeObjectCodeName =
                        crimeObjectElement.getElementsByTagName("facultative-crime-object").item(0).getTextContent();

                logger.debug(facultativeCrimeObjectCodeName);

                if (!facultativeCrimeObjectCodeName.equals(EMPTY_TEXT_NODE_VAL)) {
                    facultativeCrimeObject = crimeObjectService.
                            getFacultativeCrimeObjectByCodename(facultativeCrimeObjectCodeName);
                }

                // getting supplementary crime object

                String supplementaryCrimeObjectCodeName =
                        crimeObjectElement.getElementsByTagName("supplementary-crime-object").item(0).getTextContent();

                logger.debug(supplementaryCrimeObjectCodeName);

                if (!supplementaryCrimeObjectCodeName.equals(EMPTY_TEXT_NODE_VAL)) {
                    supplementaryCrimeObject = crimeObjectService.
                            getSupplementaryCrimeObjectByCodename(supplementaryCrimeObjectCodeName);
                }

            }

            // getting crime subject

            Node crimeSubjectNode = doc.getElementsByTagName("crime-subject").item(0);

            String crimeSubjectCodeName = crimeSubjectNode.getTextContent();

            crimeSubject = crimeSubjectService.getCrimeSubjectByCodename(crimeSubjectCodeName);

            // getting crime consequences

            Node crimeConsequenceNode = doc.getElementsByTagName("crime-consequence").item(0);

            String crimeConsequenceCodeName = crimeConsequenceNode.getTextContent();

            crimeConsequence = crimeConsequenceService.getCrimeConsequenceByCodeName(crimeConsequenceCodeName);

            // getting subjective side

            Element subjectiveSide = (Element) doc.getElementsByTagName("subjective-side").item(0);
            Element guilt = (Element) subjectiveSide.getElementsByTagName("guilt").item(0);

            String intentCodename = guilt.getElementsByTagName("intent").item(0).getTextContent();
            intent = subjectiveSideService.getIntentByCodename(intentCodename);

            String indiscretionCodename = guilt.getElementsByTagName("indiscretion").item(0).getTextContent();
            indiscretion = subjectiveSideService.getIndiscretionByCodename(indiscretionCodename);

            String motiveCodename = subjectiveSide.getElementsByTagName("motive").item(0).getTextContent();
            motive = subjectiveSideService.getMotiveByCodename(motiveCodename);

            String purposeCodename = subjectiveSide.getElementsByTagName("purpose").item(0).getTextContent();
            purposeEntity = subjectiveSideService.getPurposeByCodename(purposeCodename);

            String delinquentStateCodename = subjectiveSide.getElementsByTagName("delinquent-state")
                    .item(0).getTextContent();
            delinquentState = subjectiveSideService.getDelinquentStateByCodename(delinquentStateCodename);

            Guilt g = new Guilt(intent, indiscretion);
            subjSide = new SubjectiveSide(g, motive, purposeEntity, delinquentState);

        } catch (Exception e) {

            throw new IllegalStateException("Unparseable XML \"" + pathToFile + "\"" + ".", e);

        }


        // TODO: refactor to static builder!

        // building the whole crime object
        // setting main object
        crimeObject.setCrimeObjectEntity(mainCrimeObject);
        //crimeObject.setWithFacultativeObject(false);
        //crimeObject.setWithSupplementaryObject(false);

        // setting facultative object

        if (facultativeCrimeObject.getCodeName() != null) {
            crimeObject.setFacultativeCrimeObjectEntity(facultativeCrimeObject);
            crimeObject.setWithFacultativeObject(true);
        } else {
            crimeObject.setWithFacultativeObject(false);
        }

        // setting supplementary object

        if (supplementaryCrimeObject.getCodeName() != null) {
            crimeObject.setSupplementaryCrimeObjectEntity(supplementaryCrimeObject);
            crimeObject.setWithSupplementaryObject(true);
        } else {
            crimeObject.setWithSupplementaryObject(false);
        }

        // setting whole crime object to crime details
        crimeDetails.setCrimeObject(crimeObject);

        // crime subject turn
        crimeDetails.setCrimeSubject(crimeSubject);

        // crime consequence
        crimeDetails.setCrimeConsequence(crimeConsequence);

        //subjective side
        crimeDetails.setSubjectiveSide(subjSide);

        //done
        return crimeDetails;

    }

    @Override
    public CrimeDetails getTaskSolution(Integer taskId) {
        String pathToSolutionXML = XML_BASE_PATH + "tasks\\task_" + String.valueOf(taskId) + ".xml";
        return parseXMLForCrimeDetails(pathToSolutionXML);

    }

    /**
     * @return
     */
    @Override
    public List<CrimeDetails> retrieveAllCodexEntries() {

        List<File> codexFilesList = new ArrayList<>();
        File[] files = new File(CODEX_PATH).listFiles();

        for (File file : files) {
            if (file.isFile()) {
                codexFilesList.add(file);
            }
        }

        logger.debug("Total codex entries: " + codexFilesList.size());

        List<CrimeDetails> codexArticles = new ArrayList<>();

        for (File codexEntry : codexFilesList) {
            CrimeDetails codexEntryCrimeDetails = parseXMLForCrimeDetails(codexEntry.getAbsolutePath());
            logger.debug(getLegalNormFromCodexXMLEntry(codexEntry.getAbsolutePath()).toString());
            codexArticles.add(codexEntryCrimeDetails);
        }

        return codexArticles;

    }

    /**
     * @return
     */
    @Override
    public LegalNorm getLegalNormFromCodexXMLEntry(String pathToFile) {

        LegalNorm legalNorm = new LegalNorm();

        try {

            File fXmlFile = new File(pathToFile);
            DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
            Document doc = dBuilder.parse(fXmlFile);

            doc.getDocumentElement().normalize();

            logger.debug("Root element :" + doc.getDocumentElement().getNodeName());

            Element legalNormElement = (Element) doc.getElementsByTagName("legalnorm").item(0);

            legalNorm.setArticleNo(legalNormElement.getAttribute("article"));
            legalNorm.setArticlePart(legalNormElement.getAttribute("part"));

        } catch (Exception e) {

            throw new IllegalStateException("Unparseable XML \"" + pathToFile + "\"" + ".", e);

        }

        return legalNorm;

    }


}
